/*
 * Copyright 2009 Tran Nhut Trung.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.eclipse.core.databinding.observable.gwt.internal;

import com.google.gwt.user.client.ui.ListBox;

import com.google.gwt.user.client.ui.Widget;

import java.util.ArrayList;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.databinding.BindingException;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.gwt.IWidgetObservable;
import org.eclipse.core.databinding.observable.list.ListDiffEntry;
import org.eclipse.core.databinding.observable.list.ObservableList;

// TODO: rename to AbstractWidgetObservableList? (to use for both ListBox, Table, etc?)
public abstract class ListBoxObservableList extends ObservableList implements IWidgetObservable{
  protected ListBox listBox;
  protected String valuePropName;
  protected String displayPropName;
  private boolean updating = false;

  public ListBoxObservableList(ListBox listBox, String valuePropName, String displayPropName) {
    this(Realm.getDefault(), listBox, valuePropName, displayPropName);
  }
  public ListBoxObservableList(Realm realm, ListBox listBox, String valuePropName, String displayPropName) {
    super(realm, new ArrayList(), String.class);
    
    this.listBox = listBox;
    this.valuePropName = valuePropName;
    if (displayPropName == null){
      displayPropName = valuePropName;
    }
    this.displayPropName = displayPropName;
  }

  public Widget getWidget() {
    return listBox;
  }

  public boolean add(Object element) {
    updating = true;
    try {
      int index = wrappedList.size();
      boolean result = wrappedList.add(element);
      setValues();
      fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
          index, true, element)));
      return result;
    } finally {
      updating = false;
    }
  }

  public void add(int index, Object element) {
    updating = true;
    try {
      wrappedList.add(index, element);
      setValues();
      fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
          index, true, element)));
    } finally {
      updating = false;
    }
  }

  public boolean addAll(Collection c) {
    if (c.isEmpty()) {
      return false;
    }
    updating = true;
    try {
      int index = wrappedList.size();
      boolean result = wrappedList.addAll(c);
      setValues();
      ListDiffEntry[] entries = new ListDiffEntry[c.size()];
      int i = 0;
      for (Iterator it = c.iterator(); it.hasNext();) {
        Object o = it.next();
        entries[i++] = Diffs.createListDiffEntry(index++, true, o);
      }
      fireListChange(Diffs.createListDiff(entries));
      return result;
    } finally {
      updating = false;
    }
  }

  public boolean addAll(int index, Collection c) {
    if (c.isEmpty()) {
      return false;
    }
    updating = true;
    try {
      boolean result = wrappedList.addAll(index, c);
      setValues();
      ListDiffEntry[] entries = new ListDiffEntry[c.size()];
      int i = 0;
      for (Iterator it = c.iterator(); it.hasNext();) {
        Object o = it.next();
        entries[i++] = Diffs.createListDiffEntry(index++, true, o);
      }
      fireListChange(Diffs.createListDiff(entries));
      return result;
    } finally {
      updating = false;
    }
  }

  public void clear() {
    updating = true;
    try {
      List diffEntries = new ArrayList();
      for (Iterator it = wrappedList.iterator(); it.hasNext();) {
        Object o = it.next();
        diffEntries.add(Diffs.createListDiffEntry(0, false, o));
      }
      wrappedList.clear();
      setValues();
      fireListChange(Diffs.createListDiff((ListDiffEntry[]) diffEntries
          .toArray(new ListDiffEntry[diffEntries.size()])));
    } finally {
      updating = false;
    }
  }

  public Object move(int oldIndex, int newIndex) {
    getterCalled();
    updating = true;
    try {
      int size = wrappedList.size();
      if (oldIndex < 0 || oldIndex >= size)
        throw new IndexOutOfBoundsException(
            "oldIndex: " + oldIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$
      if (newIndex < 0 || newIndex >= size)
        throw new IndexOutOfBoundsException(
            "newIndex: " + newIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$
      if (oldIndex == newIndex)
        return wrappedList.get(oldIndex);
      Object element = wrappedList.remove(oldIndex);
      wrappedList.add(newIndex, element);
      setValues();
      fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
          oldIndex, false, element), Diffs.createListDiffEntry(
          newIndex, true, element)));
      return element;
    } finally {
      updating = false;
    }
  }

  public Object remove(int index) {
    getterCalled();
    updating = true;
    try {
      Object oldElement = wrappedList.remove(index);
      setValues();
      fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
          index, false, oldElement)));
      return oldElement;
    } finally {
      updating = false;
    }
  }

  public boolean remove(Object o) {
    getterCalled();
    int index = wrappedList.indexOf(o);
    if (index == -1) {
      return false;
    }
    updating = true;
    try {
      Object oldElement = wrappedList.remove(index);
      setValues();
      fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
          index, false, oldElement)));
      return true;
    } finally {
      updating = false;
    }
  }

  public boolean removeAll(Collection c) {
    getterCalled();
    boolean changed = false;
    updating = true;
    try {
      List diffEntries = new ArrayList();
      for (Iterator it = c.iterator(); it.hasNext();) {
        Object o = it.next();
        int index = wrappedList.indexOf(o);
        if (index != -1) {
          changed = true;
          Object oldElement = wrappedList.remove(index);
          diffEntries.add(Diffs.createListDiffEntry(index, false,
              oldElement));
        }
      }
      if (changed) {
        setValues();
        fireListChange(Diffs
            .createListDiff((ListDiffEntry[]) diffEntries
                .toArray(new ListDiffEntry[diffEntries.size()])));
      }
      return changed;
    } finally {
      updating = false;
    }
  }

  public boolean retainAll(Collection c) {
    getterCalled();
    boolean changed = false;
    updating = true;
    try {
      List diffEntries = new ArrayList();
      int index = 0;
      for (Iterator it = wrappedList.iterator(); it.hasNext();) {
        Object o = it.next();
        boolean retain = c.contains(o);
        if (retain) {
          index++;
        } else {
          changed = true;
          it.remove();
          diffEntries.add(Diffs.createListDiffEntry(index, false, o));
        }
      }
      if (changed) {
        setValues();
        fireListChange(Diffs
            .createListDiff((ListDiffEntry[]) diffEntries
                .toArray(new ListDiffEntry[diffEntries.size()])));
      }
      return changed;
    } finally {
      updating = false;
    }
  }

  public Object set(int index, Object element) {
    getterCalled();
    updating = true;
    try {
      Object oldElement = wrappedList.set(index, element);
      setValues();
      fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
          index, false, oldElement), Diffs.createListDiffEntry(index,
          true, element)));
      return oldElement;
    } finally {
      updating = false;
    }
  }
  
  private void setValues() {
    primSetValues(new ArrayList(wrappedList));
  }

  private void primSetValues(Object newValue) {
    Exception ex = null;
    try {
      List list = (List)newValue;
      this.listBox.clear();
      for (int i=0; i<list.size(); i++){
        Object obj = list.get(i);
        setValue(i, obj);
      }

      return;
    } catch (IllegalArgumentException e) {
      ex = e;
    }
    throw new BindingException("Could not write collection values", ex); //$NON-NLS-1$
  }
  
  protected abstract void setValue(int index, Object obj);
}
